/*********************************************************************************
 * Software License Agreement (BSD License)
 * Copyright (c) 2012, Carlos D. Correa (info@ngraph.org)
 *               2011, Nick Leaf (UC Davis)
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of netzen nor the
 *    names of its contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *********************************************************************************/
#include "fm3layout.h";
#include "graph.h";
#include "controller.h"


FM3Layout::FM3Layout(const char *name): Layout("FM3", name) {

    this->setPropertyBool("Enabled",false);

    // Nick's parametors for FM3
    this->setPropertyFloat("01_initialT",1.5f);
    this->setPropertyInt("02_coarsestSteps",250);
    this->setPropertyInt("03_finestSteps",30);
    this->setPropertyFloat("04_lambda",0.95f);
    this->setPropertyFloat("05_eta",0.000001f);
    this->setPropertyFloat("06_repConst",1.0f); //node-node repulsive force constant
    this->setPropertyFloat("07_desLength",0.025f); ////spring desired length

    this->setPropertyInt("08_coarseningThresh",2000);//20
    this->setPropertyFloat("09_initialTDecay",1.f);
    float f10 = sqrt(7.f/4.f);
    this->setPropertyFloat("10_desLengthDecay",f10);

    this->setPropertyFloat("11_theta",1.2f);
    this->setPropertyFloat("12_err",0.01);
    this->setPropertyFloat("13_omega",0.5f);

    this->setPropertyFloat("14_correlatingConst",10.0);


    this->setPropertyInt("Iterations",1);



    this->gpulayout = 0;

    this->setPropertyBool("ifSetNodeInitialPosAccordingToGraph",false);
    this->setPropertyBool("0519_ifNodeLinkGraphNotChanged",true);
    this->setPropertyBool("0519_updateNodePosNow",true);


    timer = createTimer(20, this);


}

void FM3Layout::update(string property){
    Layout::update(property);

    bool enabled = this->getPropertyBool("Enabled");
    assert(timer);
    if(enabled){
        timer->start();
    }
    else{
        timer->stop();
    }

}

void FM3Layout::setupFM3parameters(){
    float f_01_initialT = this->getPropertyFloat("01_initialT");
    int i_02_coarsestSteps = this->getPropertyInt("02_coarsestSteps");
    int i_03_finestSteps = this->getPropertyInt("03_finestSteps");
    float f_04_lambda = this->getPropertyFloat("04_lambda");
    float f_05_eta = this->getPropertyFloat("05_eta");
    float f_06_repConst = this->getPropertyFloat("06_repConst");
    float f_07_desLength = this->getPropertyFloat("07_desLength");

    int i_08_coarseningThresh = this->getPropertyInt("08_coarseningThresh");
    float f_09_initialTDecay = this->getPropertyFloat("09_initialTDecay");
    float f_10_desLengthDecay = this->getPropertyFloat("10_desLengthDecay");

    float f_11_theta = this->getPropertyFloat("11_theta");
    float f_12_err = this->getPropertyFloat("12_err");
    float f_13_omega = this->getPropertyFloat("13_omega");

    float f_14_correlatingConst = this->getPropertyFloat("14_correlatingConst");


    // default coarsen strength
    GM3Layout::coarseningThresh = 2000; //graph size to stop coarsening at //64*30


    GM3Layout::initialT = f_01_initialT;
    GM3Layout::coarsestSteps = i_02_coarsestSteps;
    GM3Layout::finestSteps = i_03_finestSteps;
    GM3Layout::lambda = f_04_lambda;
    GM3Layout::eta = f_05_eta;
    GM3Layout::repConst = f_06_repConst;
    GM3Layout::desLength = f_07_desLength;

    GM3Layout::coarseningThresh = i_08_coarseningThresh;
    GM3Layout::initialTDecay = f_09_initialTDecay;
    GM3Layout::desLengthDecay = f_10_desLengthDecay;

    GM3Layout::theta = f_11_theta;
    GM3Layout::err = f_12_err;
    GM3Layout::omega = f_13_omega;

    GM3Layout::correlatingConst = f_14_correlatingConst;


}

void FM3Layout::compute(){

    //if(listener) listener->setProgress(0);


    int numIterations = this->getPropertyInt("Iterations");
    for(int i=0; i<numIterations; i++){
        printf("."); fflush(stdout);

        updateVisible(); //save node index to visible vec

        computeIteration(false);
    }
    printf("\n");
}



void FM3Layout::timerEvent(){
    this->updateVisible();
    this->computeIteration(false);
    this->controller->updateRender();
}


//bool FM3Layout::setupGraphToGPU(){

//}


void FM3Layout::computeIteration(bool ifPrintMsg=false){


    assert(graph);
    if(this->gpulayout==0){

        this->gpulayout = new GPULayout();
        cout << "create GPULayout for the first time" << endl;
    }
    else{
        delete this->gpulayout;
        this->gpulayout = new GPULayout();
        cout << "recreate GPULayout!!" << endl;
    }

    setupFM3parameters();

    // clear mapping
//    this->mapIDtoNEWID.clear();
//    this->mapNEWIDtoID.clear();
//    this->mapIDtoNEWID_previous.clear();



    // ref volvis code in fm3layoutMin.cpp doFM3Layout(int t)
    bool ifResetGPUgraph = true;
    if(this->fm3edges.size()!=0 || this->fm3nodes.size()!=0 ){
        bool ifGraphStatic = this->getPropertyBool("0519_ifNodeLinkGraphNotChanged");
        if(ifGraphStatic){
            ifResetGPUgraph = false;
        }
    }
    bool ifNdEgSet = false;
    if(ifResetGPUgraph){
        ifNdEgSet = setupGPU_node_edge(); //set up fm3nodes and fm3edges
        if(!ifNdEgSet){
            return;
        }
    }

    bool ifUpdateNodePos = this->getPropertyBool("0519_updateNodePosNow");
    if(ifUpdateNodePos){
        updateNodePosNow();
    }

    // set max number of edges and nodes for GPU memory initlization
    //this->maxN = this->numNodes();
    this->maxN = this->fm3nodes.size();
    //this->maxE = this->numEdges()*2;
    this->maxE = this->fm3edges.size()*2;


    GM3Graph *g = new GM3Graph(this->fm3nodes, this->fm3edges);
    this->gpulayout->loadGraph(g, this->maxN, this->maxE,false);


    GM3Graph *g2 = this->gpulayout->computeLayout();
    bool ifNdRetrieved = this->setNodeAfterLayout_withGraph(g2);
    //this->setNo


}

void FM3Layout::updateVisible(){
    this->visible.clear();

    for(int i=0; i<this->numNodes(); i++){
        if( this->isNodeInLayout(i) ){
            visible.push_back(i);
        }
        else{
            // if the node is not in layout.
        }
    }

}

bool FM3Layout::setupGPU_node_edge(){

    int vN = this->visible.size(); //this->numNodes();

    //////////////////////0519
    this->fm3edges.clear();
    this->fm3nodes.clear();
    //this->fm3nodes.resize( vN );

    // node index mapping between CPU and GPU;
    this->mapIDtoNEWID_previous.clear(); //copy to pervious vec
    for(itr=this->mapIDtoNEWID.begin(); itr!=this->mapIDtoNEWID.end(); itr++){
        this->mapIDtoNEWID_previous[itr->first] = itr->second;
    }
    this->mapIDtoNEWID.clear();
    this->mapNEWIDtoID.clear();


    bool ifSetNodeInitialPosByGraph = this->getPropertyBool("ifSetNodeInitialPosAccordingToGraph");
    if(ifSetNodeInitialPosByGraph){
        //setNodeIniPos();
    }


    // == prepare nodes to GPU graph

    int iVerCnt = 0; //new index order of pushing nodes to GPU memory
    for(unsigned int ki=0; ki<vN; ki++){ //scan all visible nodes
        int i = this->visible[ki];

        Node *ndi = this->getNode(i);
        int iNodeID = ndi->getIndex();
        int newIDX = iVerCnt;

        float x,y;
        this->getLocation(i,x,y);

        GM3Graph::node tmpNode(cvec(x,y,0.0)); // 0428 TODO: set weight of nodes according to: centrality; node degree; what else?
        fm3nodes.push_back(tmpNode);
        //this->fm3nodes[newIDX] = tmpNode;

        mapIDtoNEWID[iNodeID] = newIDX; //iVerCnt;
        mapNEWIDtoID[newIDX] = iNodeID; // order of node pushing to FM3Graph.

        iVerCnt++;

    }


    // == prepare edges to GPU graph

    // these mapping check if edge have been saved twice.
    //    map<int, map<int, int> > mapEdgeSaved; // map<fromNodeID, map<toNodeID, multiplicityTime>
    //    map<int, map<int, int> >::iterator itrFromNode;
    //    map<int,int>::iterator itrToooNode;
    this->mapEdgeSaved.clear();
    //    this->itrFromNode = this->mapEdgeSaved.begin();
    //    this->itrToooNode = this->itrFromNode->second;


    // scan all edges of all nodes;
    for(unsigned int ki=0; ki<visible.size(); ki++){

        int i = visible[ki];
        Node *ndi = this->getNode(i); // must be visible
        int iNodeID = ndi->getIndex();

        for(int q=0; q<this->numNeighbors(i); q++){
            int jNodeID = this->getNeighborIndex(i,q);

            bool ifNodeJok = this->isNodeInLayout(jNodeID); //check if neighbor j visible too
            if(!ifNodeJok){
                continue; //skip this node.
            }


            // check if this edge have been saved
            bool ifFromNodeMatch = false;
            bool ifToooNodeMatch = false;
            int iWeightFound = 0;
            itrFromNode = mapEdgeSaved.find(iNodeID);
            if( itrFromNode!=mapEdgeSaved.end() ){ // from node check passed.
                ifFromNodeMatch = true;
                map<int,int> mapToEdge = itrFromNode->second;
                itrToooNode = mapToEdge.find( jNodeID );
                if( itrToooNode!= mapToEdge.end() ){
                    ifToooNodeMatch = true;
                    iWeightFound = itrToooNode->second;

                    // append 1 to weight of this edge.
                    iWeightFound += 1;
                    ( (itrFromNode->second).find(jNodeID) )->second = iWeightFound;
                    cout << "[multiple] (#" << iNodeID << " , #" << jNodeID << ") " << iWeightFound << endl;
                }
                else{ //new TO node.
                    iWeightFound = 1;
                    //(mapEdgeSaved[iNodeID])[jNodeID]
                    (itrFromNode->second)[jNodeID] = iWeightFound;
                    //( (itrFromNode->second).find(jNodeID) )->second = iWeightFound;
                }
            }
            if(!ifFromNodeMatch && !ifToooNodeMatch){ //this edge first time found
                map<int,int> mapToooNode;
                iWeightFound = 1;
                mapToooNode[jNodeID] = iWeightFound; //unit weight
                mapEdgeSaved[iNodeID] = mapToooNode;
            }
            //            else if(ifFromNodeMatch && !ifToooNodeMatch){
            //                iWeightFound = 1;
            //            }
        }
    }

    // then create edges at the graph for GPU
    int iEdgeCnt = 0;
    for(itrFromNode=mapEdgeSaved.begin(); itrFromNode!=mapEdgeSaved.end(); itrFromNode++){
        int iNodeID = itrFromNode->first;

        map<int,int> mapToooNode = itrFromNode->second;
        for(itrToooNode=mapToooNode.begin(); itrToooNode!=mapToooNode.end(); itrToooNode++ ){
            int jNodeID = itrToooNode->first;
            int iWeight = itrToooNode->second;

            // only consider nodes in the same cluster? filter out here.
            int iNode_newID = -1;
            int jNode_newID = -1;
            if( mapIDtoNEWID.find(iNodeID)!=mapIDtoNEWID.end() ){
                iNode_newID = mapIDtoNEWID[iNodeID];
            }
            if( mapIDtoNEWID.find(jNodeID) != mapIDtoNEWID.end() ){
                jNode_newID = mapIDtoNEWID[jNodeID];
            }
            if(iNode_newID!=-1 && jNode_newID!=-1){
                // save here

                GM3Graph::edge tmpEdge(iNode_newID, jNode_newID, 1.0*iWeight);
                this->fm3edges.push_back(tmpEdge);  //

                iEdgeCnt++;

            }
            else{
                int cc = 10;
                cc++;
            }


        }

    }
    //////////////////////0519

    return true;

}

bool FM3Layout::updateNodePosNow(){
    // update node position in fm3nodes from vnodes.
    map<int,int>::iterator itrNEWtoOLD = this->mapNEWIDtoID.begin();
    map<int,int>::iterator itrOLDtoNEW = this->mapIDtoNEWID.begin();

    for(itrNEWtoOLD=this->mapNEWIDtoID.begin(); itrNEWtoOLD!=this->mapNEWIDtoID.end(); itrNEWtoOLD++){
        int iNewID = itrNEWtoOLD->first;
        int iOldID = itrNEWtoOLD->second;

        float px, py;
        this->getLocation(iOldID, px, py); //location got from vnode

        //GM3Graph::node*tmpNode0 = this->fm3nodes[iNewID];

        GM3Graph::node tmpNode2(cvec(px,py,0.0));
        this->fm3nodes[iNewID] = tmpNode2;
    }

}


bool FM3Layout::setNodeAfterLayout_withGraph(GM3Graph *g){
    map<int,int>::iterator itID;

    bool allOK = true;
    for(itID=this->mapNEWIDtoID.begin(); itID!=this->mapNEWIDtoID.end(); itID++){
        int newID = itID->first;
        int iNodeID = itID->second;

        float newxy[2]= {0.0, 0.0};
        g->getPos(newID,newxy); //kdtree order

        // set vnodes
        if(this->isNodeInLayout(iNodeID)){
            this->setLocation(iNodeID,newxy[0], newxy[1] );
        }

        else{
            allOK=false;
        }

    }

    return allOK;
}
void FM3Layout::reset(){
    for(unsigned int i=0;i<graph->nodes.size();i++) {
        float x,y;
        x = randomFM3(-1,1);
        y = randomFM3(-1,1);
        setLocation(i,x,y);
        //setVelocity(i,0,0);
    }
}

bool FM3Layout::setNodeIniPos(){ //see layout::setGraph.
    // don't care if node is visible or not.

    assert(graph);

    //get view; // TODO

    //for(unsigned int ki=0; ki<visible.size(); ki++){
    for(unsigned int i=0; i<this->graph->nodes.size(); i++){
        //int i = visible[ki];

//        if( isNodeInLayout(i) ){
            float fx, fy;
            this->getLocation(i,fx,fy); //from vnode in this layout
            NodeInfo n;
            n.x = fx;
            n.y = fy;
            n.vx = 0;
            n.vy = 0;

            //vnodes.push_back(n);.at(i)

//        }

    }

    return true;

}

//void FM3Layout::setGraph(Graph *g){
//    assert(g);
//    this->graph = g;

//    if(){

//    }
//}


Layout *createFM3Layout(string name){
    FM3Layout *layout = new FM3Layout(name.c_str());
    return layout;
}
